package org.xlp.fraction;

import org.xlp.assertion.AssertUtils;
import org.xlp.utils.XLPVerifedUtil;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;

/**
 * @version 1.0
 * @author 徐龙平
 *         <p>
 *         2017-5-8
 *         </p>
 * 
 *         分数实现，功能主要有分数的四则运算，分数化简，主要针对大分子与大母组成的分数
 */
public class BigFraction extends IFraction<BigFraction, BigInteger> {
	private static final long serialVersionUID = -789050339844470065L;

	/**
	 * 默认保留小数位数
	 */
	private static final int ACCURACY = 6;

	/**
	 * 分子
	 */
	private BigInteger numerator;
	/**
	 * 分母
	 */
	private BigInteger denominator;

	// 常量
	private final static BigInteger INTEGER_1 = BigInteger.ONE;
	private final static BigInteger INTEGER_0 = BigInteger.ZERO;
	private final static BigInteger INTEGER_NEGATIVE_1 = new BigInteger("-1");

	// 值为0的分数常量
	public static final BigFraction FRACTION_VALUE_OF_0 = new BigFraction(
			INTEGER_0, INTEGER_1);
	// 值为1的分数常量
	public static final BigFraction FRACTION_VALUE_OF_1 = new BigFraction(
			INTEGER_1, INTEGER_1);
	// 值为-1的分数常量
	public static final BigFraction FRACTION_VALUE_OF_NRGATE_1 = new BigFraction(
			INTEGER_NEGATIVE_1, INTEGER_1);

	private BigFraction() {}

	/**
	 * 构造函数
	 * 
	 * @param numerator
	 *            分子
	 * @param denominator
	 *            分母
	 * @throws IllegalArgumentException
	 *             假如参数为null分母为0，抛出此异常
	 */
	public BigFraction(BigInteger numerator, BigInteger denominator) {
		setDenominator(denominator);
		setNumerator(numerator);
	}

	/**
	 * 构造函数
	 * 
	 * @param numerator
	 *            分子
	 * @param denominator
	 *            分母
	 * @throws IllegalArgumentException
	 *             假如参数格式错误，抛出此异常
	 */

	public BigFraction(String numerator, String denominator) {
		if (!XLPVerifedUtil.isInteger(numerator))
			throw new IllegalArgumentException("参数格式错误，错误参数是：[" + numerator
					+ "]");
		if (denominator == null || denominator.matches(ZERO_REGEX)
			|| !XLPVerifedUtil.isInteger(denominator))
			throw new IllegalArgumentException("参数格式错误，错误参数是：[" + denominator
					+ "]");
		setNumerator(new BigInteger(numerator.trim()));
		setDenominator(new BigInteger(denominator.trim()));
	}

	/**
	 * 以分子，分母，构造分数对象
	 * 
	 * @param numerator
	 *            分子
	 * @param denominator
	 *            分母
	 * @throws IllegalArgumentException
	 *             假如分母为0，抛出此异常
	 */
	public BigFraction(long numerator, long denominator) {
		this(new BigInteger(String.valueOf(numerator)),
				new BigInteger(String.valueOf(denominator)));
	}

	/**
	 * 用{@link IFraction}构造分数对象
	 * 
	 * @param fraction
	 *            XLPFraction对象
	 * @throws IllegalArgumentException
	 *             假如参数为null，抛出此异常
	 */
	public BigFraction(IFraction fraction) {
		if (fraction == null)
			throw new IllegalArgumentException("参数必须不为null");
		setNumerator(new BigInteger(fraction.getNumerator().toString()));
		setDenominator(new BigInteger(fraction.getDenominator().toString()));
	}

	/**
	 * 得到分子(可能返回的是转换后的分子)
	 * 
	 * @return
	 */
	@Override
	public BigInteger getNumerator() {
		return numerator;
	}

	/**
	 * 设置分子
	 * 
	 * @param numerator
	 * @throws IllegalArgumentException
	 *             假如参数为null，抛出该异常
	 * 
	 */
	protected void setNumerator(BigInteger numerator) {
		if (numerator == null)
			throw new IllegalArgumentException("参数必须不为null");
		this.numerator = numerator;
	}

	/**
	 * 得到分母(可能返回的是转换后的分母)
	 * 
	 * @return
	 */
	@Override
	public BigInteger getDenominator() {
		return denominator;
	}

	/**
	 * 设置分母
	 * 
	 * @param denominator
	 * @throws IllegalArgumentException
	 *             假如参数为null或0，抛出该异常
	 */
	protected void setDenominator(BigInteger denominator) {
		if (denominator == null || denominator.compareTo(INTEGER_0) == 0)
			throw new IllegalArgumentException("参数必须不为null或0");
		this.denominator = denominator;
		if (this.denominator.compareTo(INTEGER_0) < 0) {
			this.numerator = this.numerator.negate();
			this.denominator = this.denominator.negate();
		}
	}

	/**
	 * 比较两个分数的大小
	 * 
	 * @param fraction
	 * @return 假如this > o 返回1，假如this = o 返回0，假如this < o 返回-1
	 */
	@Override
	public int compareTo(IFraction fraction) {
		if (fraction instanceof BigFraction){
			BigFraction f = (BigFraction) fraction;
			if (getDenominator().equals(f.getDenominator())){
				return getNumerator().compareTo(f.getNumerator());
			}
			BigInteger result = getNumerator().multiply(f.getDenominator());
			BigInteger t = f.getNumerator().multiply(getDenominator());
			result = result.subtract(t);
			return result.compareTo(INTEGER_0);
		}
		return super.compareTo(fraction);
	}

	@Override
	public String toString() {
		if (isZero())
			return "0";
		if (this.denominator.compareTo(INTEGER_1) == 0)
			return numerator.toString();

		return numerator.toString() + "/" + denominator.toString();
	}

	/**
	 * 判断分数值是否为1
	 * 
	 * @return 是返回true，否则返回false
	 */
	public boolean isOne() {
		return this.denominator.compareTo(this.numerator) == 0;
	}

	/**
	 * 判断分数值是否为0
	 * 
	 * @return 是返回true，否则返回false
	 */
	public boolean isZero() {
		return this.numerator.compareTo(INTEGER_0) == 0;
	}

	/**
	 * 判断分数值是否为-1
	 * 
	 * @return 是返回true，否则返回false
	 */
	public boolean isMinusOne() {
		return this.denominator.compareTo(this.numerator.negate()) == 0;
	}

	@Override
	public long longValue() {
		return numerator.divide(denominator).longValue();
	}

	@Override
	public double doubleValue() {
		return decimalValue().doubleValue();
	}

	/**
	 * 分数加法
	 *
	 * @param fraction
	 * @return 返回this
	 */
	@Override
	public BigFraction add(long fraction) {
		numerator = denominator.multiply(new BigInteger(String.valueOf(fraction)))
				.add(numerator);
		return this;
	}

	/**
	 * 分数加法
	 *
	 * @param fraction
	 * @return 返回this
	 */
	public BigFraction add(BigInteger fraction) {
		numerator = denominator.multiply(fraction).add(numerator);
		return this;
	}

	/**
	 * 分数加法
	 *
	 * @param fraction
	 * @return 返回this
	 */
	@Override
	public BigFraction add(double fraction) {
		return add(valueOf(fraction));
	}

	/**
	 * 分数加法
	 *
	 * @param fraction
	 * @return 返回this
	 * @throws NullPointerException 假如参数为null，则抛出该异常
	 */
	@Override
	public BigFraction add(BigFraction fraction) {
		AssertUtils.isNotNull(fraction, "fraction parameter must be not null!");
		if (fraction.denominator.equals(denominator)) {
			numerator = numerator.add(fraction.getNumerator());
		} else {
			numerator = fraction.getDenominator().multiply(numerator).add(
					fraction.getNumerator().multiply(denominator));
			setDenominator(denominator.multiply(fraction.getDenominator()));
		}
		return this;
	}

	/**
	 * 分数减法
	 *
	 * @param fraction
	 * @return 返回this
	 */
	@Override
	public BigFraction subtract(long fraction) {
		numerator = numerator.subtract(
				denominator.multiply(new BigInteger(String.valueOf(fraction))));
		return this;
	}

	/**
	 * 分数减法
	 *
	 * @param fraction
	 * @return 返回this
	 */
	public BigFraction subtract(BigInteger fraction) {
		numerator = numerator.subtract(denominator.multiply(fraction));
		return this;
	}

	/**
	 * 分数减法
	 *
	 * @param fraction
	 * @return 返回this
	 */
	@Override
	public BigFraction subtract(double fraction) {
		return subtract(valueOf(fraction));
	}

	/**
	 * 分数减法
	 *
	 * @param fraction
	 * @return 返回this
	 * @throws NullPointerException 假如参数为null，则抛出该异常
	 */
	@Override
	public BigFraction subtract(BigFraction fraction) {
		AssertUtils.isNotNull(fraction, "fraction parameter must be not null!");
		if (fraction.denominator.equals(denominator)) {
			numerator = numerator.subtract(fraction.getNumerator());
		} else {
			numerator = fraction.getDenominator().multiply(numerator).subtract(
					fraction.getNumerator().multiply(denominator));
			setDenominator(denominator.multiply(fraction.getDenominator()));
		}
		return this;
	}

	/**
	 * 分数乘法
	 *
	 * @param fraction
	 * @return 返回this
	 */
	@Override
	public BigFraction multiply(long fraction) {
		numerator = numerator.multiply(new BigInteger(String.valueOf(fraction)));
		return this;
	}

	/**
	 * 分数乘法
	 *
	 * @param fraction
	 * @return 返回this
	 */
	public BigFraction multiply(BigInteger fraction) {
		numerator = numerator.multiply(fraction);
		return this;
	}

	/**
	 * 分数乘法
	 *
	 * @param fraction
	 * @return 返回this
	 */
	@Override
	public BigFraction multiply(double fraction) {
		return multiply(valueOf(fraction));
	}

	/**
	 * 分数乘法
	 *
	 * @param fraction
	 * @return 返回this
	 * @throws NullPointerException 假如参数为null，则抛出该异常
	 */
	@Override
	public BigFraction multiply(BigFraction fraction) {
		AssertUtils.isNotNull(fraction, "fraction parameter must be not null!");
		numerator = numerator.multiply(fraction.getNumerator());
		denominator = denominator.multiply(fraction.getDenominator());
		return this;
	}

	/**
	 * 分数除法
	 *
	 * @param fraction
	 * @return 返回this
	 * @throws NullPointerException 假如参数为null，则抛出该异常
	 * @throws IllegalArgumentException 假如除数为0，则抛出该异常
	 */
	@Override
	public BigFraction divide(BigFraction fraction) {
		AssertUtils.isNotNull(fraction, "fraction parameter must be not null!");
		if (fraction.isZero()) throw new IllegalArgumentException("除数不能为0！");
		numerator = numerator.multiply(fraction.getDenominator());
		setDenominator(denominator.multiply(fraction.getNumerator()));
		return this;
	}

	/**
	 * 分数除法
	 *
	 * @param fraction
	 * @return 返回this
	 */
	@Override
	public BigFraction divide(long fraction) {
		setDenominator(denominator.multiply(new BigInteger(String.valueOf(fraction))));
		return this;
	}

	/**
	 * 分数除法
	 *
	 * @param fraction
	 * @return 返回this
	 */
	public BigFraction divide(BigInteger fraction) {
		setDenominator(denominator.multiply(fraction));
		return this;
	}

	/**
	 * 分数除法
	 *
	 * @param fraction
	 * @return 返回this
	 */
	@Override
	public BigFraction divide(double fraction) {
		return divide(valueOf(fraction));
	}

	/**
	 * 取反
	 *
	 * @return 返回一个与this相反的分数新对象
	 */
	@Override
	public BigFraction negate() {
		return new BigFraction(numerator.negate(), denominator);
	}

	/**
	 * 取绝对值
	 *
	 * @return 返回this
	 */
	@Override
	public BigFraction abs() {
		return new BigFraction(numerator.abs(), denominator);
	}

	/**
	 * 计算this的n次幂（n =0,1,2,3,....）
	 *
	 * @param n n次幂
	 * @return this的n次幂
	 * @throws IllegalArgumentException 假如出现 0的0次幂或0的负数次幂，则抛出该异常
	 */
	@Override
	public BigFraction pow(int n) {
		boolean isZero = isZero();
		if (isZero && n <= 0) throw new IllegalArgumentException("0的0次幂或0的负数次幂无意义");
		if (isZero) {
			numerator = INTEGER_0;
			denominator = INTEGER_1;
		} else if(isOne() || n == 0 || (isMinusOne() && n % 2 == 0)){
			numerator = INTEGER_1;
			denominator = INTEGER_1;
		} else if(isMinusOne()){
			return this;
		} else if (n < 0){
			n = -n;
			numerator = denominator.pow(n);
			setDenominator(numerator.pow(n));
		} else {
			numerator = numerator.pow(n);
			denominator = denominator.pow(n);
		}
		return this;
	}

	/**
	 * 获取最简分数
	 * @return 返回this
	 */
	@Override
	public BigFraction getFraction() {
		BigInteger des1 = numerator.compareTo(INTEGER_0) < 0 ? numerator.negate() : numerator;
		BigInteger mbc = des1.gcd(denominator);
		return new BigFraction(numerator.divide(mbc), denominator.divide(mbc));
	}

	/**
	 * 分数转换成指定的精度的小数BigDecimal
	 *
	 * @param accuracy 假如accuracy小于0，则重新设置为0
	 * @return
	 */
	@Override
	public BigDecimal decimalValue(int accuracy) {
		accuracy = Math.max(accuracy, 0);
		return new BigDecimal(numerator.toString()).divide(new BigDecimal(denominator), accuracy,
				RoundingMode.HALF_UP);
	}

	/**
	 * 分数转换成小数BigDecimal
	 *
	 * @return
	 */
	@Override
	public BigDecimal decimalValue() {
		return decimalValue(ACCURACY);
	}

	/**
	 * 分数转换成BigInteger
	 *
	 * @return
	 */
	@Override
	public BigInteger bigIntegerValue() {
		return numerator.divide(denominator);
	}

	/**
	 * 把浮点数转换成分数对象
	 * 
	 * @param num
	 * @return
	 */
	public static BigFraction valueOf(double num) {
		String[] split = String.valueOf(num).split("\\.");
		return valueOf0(split);
	}

	/**
	 * 把浮点数转换成分数对象
	 * 
	 * @param num
	 * @return
	 * @throws IllegalArgumentException
	 *             假如参数为null，抛出此异常
	 */
	public static BigFraction valueOf(BigDecimal num) {
		if (num == null)
			throw new IllegalArgumentException("参数必须不为null");
		String[] split = num.toPlainString().split("\\.");
		return valueOf0(split);
	}

	/**
	 * 用字符串数组构造分数对象,数组存储的是小数的整数部分与小数部分
	 * 
	 * @param num
	 * @return
	 */
	private static BigFraction valueOf0(String[] num) {
		BigFraction fraction = new BigFraction();
		if (num.length == 1 || INTEGER_0.compareTo(new BigInteger(num[1])) == 0) {
			fraction.setNumerator(new BigInteger(num[0]));
			fraction.setDenominator(INTEGER_1);
		} else {
			int len = num[1].length();
			if ("".equals(num[0])) {
				num[0] = "0";
			}
			BigInteger des1 = new BigInteger(num[0]);
			BigInteger mis1 = new BigInteger(num[1]);
			BigInteger ten = BigInteger.valueOf(10);
			BigInteger pow = ten.pow(len);
			fraction.setNumerator(pow.multiply(des1).add(mis1));
			fraction.setDenominator(pow);
		}
		return fraction;
	}

	/**
	 * 把分数字符串转换成分数对象
	 * 
	 * @param fraction
	 * @return
	 * @throws IllegalArgumentException
	 *             假如参数格式错误，抛出该异常
	 * @throws NullPointerException
	 *     		 假如参数为null，则抛出该异常
	 */
	public static BigFraction valueOf(String fraction) {
		AssertUtils.isNotNull(fraction, "fraction parameter must be not null!");
		fraction = fraction.trim();
		if (!XLPVerifedUtil.isFraction(fraction)) {
			throw new IllegalArgumentException("参数格式错误，param:[" + fraction + "]");
		}

		String[] rs = fraction.trim().split("[/]");
		if (rs.length >= 2 && new BigInteger(rs[1]).compareTo(INTEGER_0) == 0) {
			throw new IllegalArgumentException("参数格式错误，param:[" + fraction + "]");
		}
		return valueOf0(rs);
	}

	/**
	 * 把给定的分数对象转换成<code>XLPBigFraction</code>对象
	 * 
	 * @param fraction
	 * @return
	 */
	public static BigFraction toBigFraction(IFraction fraction) {
		if (fraction == null)
			return null;
		return new BigFraction(fraction);
	}

	/**
	 * 使用小数字符串创建分数对象
	 * @param decimal
	 * @return
	 * @throws NullPointerException 假如参数为null或空，则抛出该异常
	 * @throws IllegalArgumentException 假如参数解析不了则抛出该异常
	 */
	public static BigFraction fromDecimalStr(String decimal){
		AssertUtils.isNotNull(decimal, "decimal parameter must be not null or empty!");
		if (!XLPVerifedUtil.isNumber(decimal)){
			throw new IllegalArgumentException(decimal + "：不是合法的实数字符串");
		}
		String[] split = decimal.split("\\.");
		return valueOf0(split);
	}
}
